/*=============================================================================
	CommonRender.cpp: Crytek Common render helper functions and structures declarations.
	Copyright (c) 2001 Crytek Studios. All Rights Reserved.

	Revision history:
		* Created by Honich Andrey

=============================================================================*/

#include "StdAfx.h"
#include "Shadow_Renderer.h"

TArray<SRendItem> SRendItem::m_RendItems[RT_COMMAND_BUF_COUNT][MAX_LIST_ORDER][EFSLIST_NUM];  

int SRendItem::m_RenderView[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];
int SRendItem::m_RecurseLevel[RT_COMMAND_BUF_COUNT];
int SRendItem::m_AppStartRI[MAX_REND_RECURSION_LEVELS][MAX_LIST_ORDER][EFSLIST_NUM];

int SRendItem::m_StartFrust[RT_COMMAND_BUF_COUNT][MAX_REND_LIGHTS+MAX_DEFERRED_LIGHTS];;
int SRendItem::m_EndFrust[RT_COMMAND_BUF_COUNT][MAX_REND_LIGHTS+MAX_DEFERRED_LIGHTS];
int SRendItem::m_ShadowsStartRI[RT_COMMAND_BUF_COUNT][MAX_SHADOWMAP_FRUSTUMS];
int SRendItem::m_ShadowsEndRI[RT_COMMAND_BUF_COUNT][MAX_SHADOWMAP_FRUSTUMS];

SRendLightGroup SRendItem::m_RenderLightGroups[MAX_SORT_GROUPS][MAX_REND_LIGHT_GROUPS+1];
uint32 SRendItem::m_ShadowsValidMask[MAX_REND_RECURSION_LEVELS][MAX_REND_LIGHT_GROUPS];
int SRendItem::m_nSortGroups;
uint32 SRendItem::m_BatchFlags[MAX_REND_RECURSION_LEVELS][MAX_LIST_ORDER][EFSLIST_NUM];

struct CompareItemPreprocess
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		if (a.nBatchFlags != b.nBatchFlags)
			return a.nBatchFlags < b.nBatchFlags;

		return a.SortVal < b.SortVal;
	}
};

struct CompareRendItem
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		if (a.DynLMask != b.DynLMask)       // Sort by lights
			return a.DynLMask < b.DynLMask;

		/// Nearest objects should be rendered first
		int nNearA = (a.ObjSort & FOB_NEAREST);
		int nNearB = (b.ObjSort & FOB_NEAREST);
		if (nNearA != nNearB)               // Sort by nearest flag
			return nNearA > nNearB;

		if (a.SortVal != b.SortVal)         // Sort by shaders
			return a.SortVal < b.SortVal;

		if (a.pElem != b.pElem)               // Sort by geometry
			return a.pElem < b.pElem;

		return (a.ObjSort & 0xFFFF) < (b.ObjSort & 0xFFFF);   // Sort by distance
	}
};

struct CompareRendItemZPass
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		const int layerSize = 50;  // Note: ObjSort contains round(entityDist * 2) for meshes
		
		// Sort by nearest flag
		int nNearA = (a.ObjSort & FOB_NEAREST);
		int nNearB = (b.ObjSort & FOB_NEAREST);
		if (nNearA != nNearB)
			return nNearA > nNearB;
		
		// Sort by depth/distance layers
		int depthLayerA = (a.ObjSort & 0xFFFF) / layerSize;
		int depthLayerB = (b.ObjSort & 0xFFFF) / layerSize;
		if (depthLayerA != depthLayerB)
			return depthLayerA < depthLayerB;

    if (a.nStencRef != b.nStencRef)
      return a.nStencRef < b.nStencRef;

		if (a.SortVal != b.SortVal)    // Sort by shaders
			return a.SortVal < b.SortVal;

		// Sorting by geometry less important than sorting by shaders
		//if (a.Item != b.Item)    // Sort by geometry
		//	return a.Item < b.Item;

		return (a.ObjSort & 0xFFFF) < (b.ObjSort & 0xFFFF);    // Sort by distance
	}
};

struct CompareItem_Decal
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		uint32 objSortA_Low(a.ObjSort & 0xFFFF);
		uint32 objSortA_High(a.ObjSort & ~0xFFFF);
		uint32 objSortB_Low(b.ObjSort & 0xFFFF);
		uint32 objSortB_High(b.ObjSort & ~0xFFFF);

		if (objSortA_Low != objSortB_Low)
			return objSortA_Low < objSortB_Low;

		if (a.DynLMask != b.DynLMask)
			return a.DynLMask < b.DynLMask;

		if (a.SortVal != b.SortVal)
			return a.SortVal < b.SortVal;

		return objSortA_High < objSortB_High;
	}
};

struct CompareItem_Terrain
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		if (a.DynLMask != b.DynLMask)
			return a.DynLMask < b.DynLMask;

		CRendElementBase *pREa = a.pElem;
		CRendElementBase *pREb = b.pElem;

		if (pREa->m_CustomTexBind[0] != pREb->m_CustomTexBind[0])
			return pREa->m_CustomTexBind[0] < pREb->m_CustomTexBind[0];

		if (pREa->m_CustomTexBind[1] != pREb->m_CustomTexBind[1])
			return pREa->m_CustomTexBind[1] < pREb->m_CustomTexBind[1];

		return a.ObjSort < b.ObjSort;
	}
};

struct CompareItem_NoPtrCompare
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		if (a.DynLMask != b.DynLMask)
			return a.DynLMask < b.DynLMask;

		if (a.ObjSort != b.ObjSort)
			return a.ObjSort < b.ObjSort;

		float pSurfTypeA = ((float*)a.pElem->m_CustomData)[8];
		float pSurfTypeB = ((float*)b.pElem->m_CustomData)[8];
		if (pSurfTypeA != pSurfTypeB)
			return (pSurfTypeA < pSurfTypeB);

		pSurfTypeA = ((float*)a.pElem->m_CustomData)[9];
		pSurfTypeB = ((float*)b.pElem->m_CustomData)[9];
		if (pSurfTypeA != pSurfTypeB)
			return (pSurfTypeA < pSurfTypeB);

		pSurfTypeA = ((float*)a.pElem->m_CustomData)[11];
		pSurfTypeB = ((float*)b.pElem->m_CustomData)[11];
		return (pSurfTypeA < pSurfTypeB);
	}
};

struct CompareDist
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		return (a.fDist > b.fDist);
	}
};

struct CompareDistInverted
{
	bool operator()(const SRendItem& a, const SRendItem& b) const
	{
		return (a.fDist < b.fDist);
	}
};

void SRendItem::mfSortPreprocess(SRendItem *First, int Num)
{
	std::sort(First, First+Num, CompareItemPreprocess());
}

void SRendItem::mfGenerateLightGroupsTransparent(SRendItem *First, int Num)
{
  assert(gRenDev->m_pRT->IsRenderThread());
	const int nThreadID = gRenDev->m_RP.m_nProcessThreadID;
	const I3DEngine * pEng = gEnv->p3DEngine;

	PrefetchLine(First, 0);

  // Create light groups
  int i, j;
  bool bPrevProcessed = false;
  uint32 nSort = 0; 
	int iMaxIndex = Num - 1;
  SRendLightGroup *pGR = &m_RenderLightGroups[nSort][0];
  for (j=0; j<=MAX_REND_LIGHT_GROUPS; j++)
  {
    pGR[j].Reset();
  }

	int iPrefetchIndex2 = 0;
  for (i=0; i<Num; i++)
  {
    SRendItem *ri = &First[i];
		const int iPrefetchIndex1 = min_branchless(i + 2, iMaxIndex);
		PrefetchLine(&First[iPrefetchIndex1], 16);
		PrefetchLine(First[iPrefetchIndex2].pObj, 0);
		iPrefetchIndex2 = iPrefetchIndex1;

    uint32 LightMask = ri->DynLMask;
    bool bProcessed = false;
    if (LightMask)
    {
      CRenderObject *pObj = ri->pObj;
#ifdef _DEBUG
      CTexture *pTex = CTexture::GetByID(pObj->m_nTextureID);
      CShader *pSh = mfGetShader(ri->SortVal);
      int nnn = 0;
#endif
      if (pObj->m_ObjFlags & FOB_INSHADOW)
      {
        int nFirstLightInGroupID = 0;
        int nPass = 0;
        if (!bPrevProcessed && nSort < MAX_SORT_GROUPS-1)
        {
          nSort++;
          pGR = &m_RenderLightGroups[nSort][0];
          for (j=0; j<=MAX_REND_LIGHT_GROUPS; j++)
          {
            pGR[j].Reset();
          }
          bPrevProcessed = true;
        }

				const CShadowUtils::ShadowFrustumIDs * pFrustList = NULL;
				if (pObj->m_bHasShadowCasters)
				{
					SRenderObjData * pOD = pObj->GetObjData(nThreadID);
					const uint64 nShadowCasters = pOD ? pOD->m_ShadowCasters : 0;
					pFrustList = CShadowUtils::GetShadowFrustumList(nShadowCasters);
				}
        for (j=0; j<MAX_REND_LIGHT_GROUPS; j++, nFirstLightInGroupID+=4)
        {
          uint32 nCurMaskGroup = 0xf<<nFirstLightInGroupID;
          if (ri->DynLMask & nCurMaskGroup)
          {
            pGR[j].m_GroupLightMask |= (ri->DynLMask & nCurMaskGroup);
            pGR[j].RendItemsLights.push_back(nPass ? i|0x80000000 : i);
            nPass++;
						uint32 bAdded[4] = {0};
						if (pFrustList)
						{
							for (CShadowUtils::ShadowFrustumIDs::const_iterator itFrID = pFrustList->begin();
								itFrID != pFrustList->end(); itFrID++)
							{
								const ShadowMapFrustum* pFr = CShadowUtils::GetFrustum(*itFrID);
								if (pFr && pFr->nDLightId >= nFirstLightInGroupID && pFr->nDLightId <= nFirstLightInGroupID+3)
								{
									int nId = pFr->nDLightId-nFirstLightInGroupID;
									if (!bAdded[nId])
									{
										bAdded[nId] = 1;
										pGR[j].RendItemsShadows[nId].push_back(i);
									}
								}
							}
						}
          }
          if (ri->DynLMask < (uint32)(1<<(nFirstLightInGroupID+4)))
            break;
        }
        ri->ObjSort = nPass << 8;
        bProcessed = true;
      }
    }
    if (!bProcessed)
    {
      ri->ObjSort = 1 << 8;
      pGR[MAX_REND_LIGHT_GROUPS].RendItemsLights.push_back(i);
      pGR[MAX_REND_LIGHT_GROUPS].m_GroupLightMask |= ri->DynLMask;
      bPrevProcessed = false;
    }
    if (i && bProcessed != bPrevProcessed && nSort < MAX_SORT_GROUPS-1)
    {
      nSort++;
      pGR = &m_RenderLightGroups[nSort][0];
      for (j=0; j<=MAX_REND_LIGHT_GROUPS; j++)
      {
        pGR[j].Reset();
      }
      bPrevProcessed = bProcessed;
    }
  }
  m_nSortGroups = nSort+1;
}

void SRendItem::mfGenerateLightGroupsOpaque(SRendItem *First, int Num)
{
	FUNCTION_PROFILER_RENDER_FLAT
  assert(gRenDev->m_pRT->IsRenderThread());
	const int nThreadID = gRenDev->m_RP.m_nProcessThreadID;
	PrefetchLine(First, 0);

  int i, j;
	int iMaxIndex = Num - 1;
  SRendLightGroup *pGR = &m_RenderLightGroups[0][0];
  for (j=0; j<MAX_REND_LIGHT_GROUPS+1; j++)
  {
    pGR[j].Reset();
  }

  for (i=0; i<Num; i++)
	{
		SRendItem *ri = &First[i];
		int iPrefetchIndex = min_branchless(i+1, iMaxIndex);
		PrefetchLine(First[iPrefetchIndex].pObj, 0);

		uint32 LightMask = ri->DynLMask;
		bool bProcessed = false;
		if (LightMask)
		{
			CRenderObject *pObj = ri->pObj;
			if (!pObj) return; 
			if (pObj->m_ObjFlags & FOB_INSHADOW)
			{
				int nFirstLightInGroupID = 0;
				int uRendItemsLightsMask = i;
				const CShadowUtils::ShadowFrustumIDs * pFrustList = NULL;
				if (pObj->m_bHasShadowCasters)
				{
					SRenderObjData * pOD = pObj->GetObjData(nThreadID);
					const uint64 nShadowCasters = pOD ? pOD->m_ShadowCasters : 0;
					pFrustList = CShadowUtils::GetShadowFrustumList(nShadowCasters);
				}
				for (j=0; j<MAX_REND_LIGHT_GROUPS; j++, nFirstLightInGroupID+=4)
				{
					uint32 nCurMaskGroup = 0xf<<nFirstLightInGroupID;
					uint32 nCurLMask = LightMask & nCurMaskGroup;
					if (nCurLMask)
					{
						pGR[j].m_GroupLightMask |= (LightMask & nCurMaskGroup);
						pGR[j].RendItemsLights.push_back(uRendItemsLightsMask);
						uRendItemsLightsMask |= 0x80000000;

						uint32 bAdded[4] = {0};
						if (pFrustList)
						{
							for (CShadowUtils::ShadowFrustumIDs::const_iterator itFrID = pFrustList->begin();
								itFrID != pFrustList->end(); itFrID++)
							{
								const ShadowMapFrustum* pFr = CShadowUtils::GetFrustum(*itFrID);
								if (pFr)
								{
									int nId = pFr->nDLightId-nFirstLightInGroupID;
									if (nId>=0 && nId<4 && !bAdded[nId])
									{
										bAdded[nId] = 1;
										pGR[j].RendItemsShadows[nId].push_back(i);
									}
								}
							}
						}
					}
					if (LightMask < (uint32)(1<<(nFirstLightInGroupID+4)))
						break;
				}
				bProcessed = true;
			}
		}
		if (!bProcessed)
		{
			pGR[MAX_REND_LIGHT_GROUPS].RendItemsLights.push_back(/*bSecondPass ? i|0x80000000 :*/ i);
			pGR[MAX_REND_LIGHT_GROUPS].m_GroupLightMask |= LightMask;
		}
	}
  m_nSortGroups = 1;
}

void SRendItem::mfSortByDist(SRendItem *First, int Num, bool bDecals,bool InvertedOrder)
{
  CRenderer *r = gRenDev;
  int i;
  if (!bDecals)
  {
		//Pre-pass to bring in the first 8 entries. 8 cache requests can be in flight on PS3/360
		const int iPrefetchLoopLastIndex = min_branchless(8, Num);
		for (i=0; i<iPrefetchLoopLastIndex; i++)
		{
			//It's safe to prefetch NULL
			PrefetchLine(First[i].pObj, offsetof(CRenderObject, m_fSort));
		}

		const int iLastValidIndex = Num - 1;

		//Note: this seems like quite a bit of work to do some prefetching but this code was generating a
		//			level 2 cache miss per iteration of the loop - Rich S
    for (i=0; i<Num; i++)
    {
      SRendItem *pRI = &First[i];
			int iPrefetchIndex = min_branchless(i + 8, iLastValidIndex);
			PrefetchLine(First[iPrefetchIndex].pObj, offsetof(CRenderObject, m_fSort));
      CRenderObject *pObj = pRI->pObj;
		  if (!pObj)	//Is this check necessary? I've never seen it be hit. Maybe #if out in _RELEASE and Fatal Error otherwise?
		  {
			  CryLogAlways("SRendItem::mfSortByDist: pObject is NULL");
			  continue;
		  }

			//We're prefetching on m_fSort, we're still getting some L2 cache misses on access to m_fDistance,
			//	but moving them closer in memory is complicated due to an aligned array that's nestled in there...
			float fAddDist = pObj->m_fSort;
      pRI->fDist = pObj->m_fDistance + fAddDist;																										
    }

		if(InvertedOrder)
		  std::sort(First, First+Num, CompareDistInverted());
		else
		  std::sort(First, First+Num, CompareDist());
  }
  else
  {
    std::sort(First, First + Num, CompareItem_Decal());
  }
}

template<class Iter,class StrictWeakOrdering>
void RendItemSort(Iter First,Iter Last,StrictWeakOrdering Comp)
{
	std::sort(First,Last,Comp);
}

void SRendItem::mfSortByLight(SRendItem *First, int Num, bool bSort, const bool bIgnoreRePtr, bool bSortDecals)
{
  if (bSort)
	{
    if (bIgnoreRePtr)
			RendItemSort(First, First + Num, CompareItem_NoPtrCompare());
		else
		{
			if (bSortDecals)
				RendItemSort(First, First + Num, CompareItem_Decal());
			else
				RendItemSort(First, First + Num, CompareRendItem());
		}
	}
}

void SRendItem::mfSortForZPass(SRendItem *First, int Num)
{
	RendItemSort(First, First + Num, CompareRendItemZPass());
}

void SRendItem::ResetRendItems()
{
	for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++ i)
	{
		for (int j = 0; j < MAX_LIST_ORDER; ++ j)
		{
			for (int k = 0; k < EFSLIST_NUM; ++ k)
			{
				m_RendItems[i][j][k].Free();
			}
		}
	}
}

//=================================================================

